package com.mzj.saas.commons.util;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassUtils
{
	private static final Logger LOG = LoggerFactory.getLogger(ClassUtils.class);

	private static Map<String, Class<?>> basicTypes;

	static
	{
		basicTypes = new HashMap<String, Class<?>>();
		basicTypes.put("boolean", boolean.class);
		basicTypes.put("char", char.class);
		basicTypes.put("byte", byte.class);
		basicTypes.put("short", short.class);
		basicTypes.put("int", int.class);
		basicTypes.put("long", long.class);
		basicTypes.put("float", float.class);
		basicTypes.put("double", double.class);
		basicTypes.put("void", void.class);
		basicTypes.put("java.lang.Boolean", Boolean.class);
		basicTypes.put("java.lang.Character", Character.class);
		basicTypes.put("java.lang.Byte", Byte.class);
		basicTypes.put("java.lang.Short", Short.class);
		basicTypes.put("java.lang.Integer", Integer.class);
		basicTypes.put("java.lang.Long", Long.class);
		basicTypes.put("java.lang.Float", Float.class);
		basicTypes.put("java.lang.Double", Double.class);

		basicTypes.put("[Z", boolean.class);
		basicTypes.put("[C", char.class);
		basicTypes.put("[B", byte.class);
		basicTypes.put("[S", short.class);
		basicTypes.put("[I", int.class);
		basicTypes.put("[J", long.class);
		basicTypes.put("[F", float.class);
		basicTypes.put("[D", double.class);

		basicTypes.put("[Ljava.lang.Boolean", Boolean.class);
		basicTypes.put("[Ljava.lang.Character", Character.class);
		basicTypes.put("[Ljava.lang.Byte", Byte.class);
		basicTypes.put("[Ljava.lang.Short", Short.class);
		basicTypes.put("[Ljava.lang.Integer", Integer.class);
		basicTypes.put("[Ljava.lang.Long", Long.class);
		basicTypes.put("[Ljava.lang.Float", Float.class);
		basicTypes.put("[Ljava.lang.Double", Double.class);
	}

	public static Class<?> getRealClass(Object _obj)
	{
		if (_obj == null)
			return null;
		else
		{
			Class<?> clz = _obj.getClass() ;
			return clz.isArray()?clz.getComponentType():clz ;
		}
	}

	public static ClassLoader newClassLoader(String[] _urls)
	{
		return newClassLoader(_urls, ClassUtils.class.getClassLoader());
	}

	public static ClassLoader newClassLoader(String[] _urls, ClassLoader _parent)
	{
		if (_urls == null || _urls.length <= 0)
			return null;

		try
		{
			URL[] urls = new URL[_urls.length];
			for (int i = 0; i < _urls.length; i++)
			{
				if (!_urls[i].toLowerCase().startsWith("http://"))
					_urls[i] = "file://" + _urls[i];

				if (_urls[i].endsWith(".jar") || _urls[i].endsWith(".class"))
					urls[i] = new URL(_urls[i]);
				else if (_urls[i].endsWith(File.pathSeparator))
					urls[i] = new URL(_urls[i]);
				else
					urls[i] = new URL(_urls[i] + File.pathSeparator);
			}
			return newClassLoader(urls, _parent);
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static ClassLoader newClassLoader(String _path)
	{
		try
		{
			URL url = new URL(_path);
			return newClassLoader(new URL[] { url });
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static ClassLoader newClassLoader(String _path, ClassLoader _parent)
	{
		try
		{
			URL url = new URL(_path);
			return newClassLoader(new URL[] { url }, _parent);
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static ClassLoader newClassLoader(URL[] _urls)
	{
		return new URLClassLoader(_urls);
	}

	public static ClassLoader newClassLoader(URL[] _urls, ClassLoader _parent)
	{
		return new URLClassLoader(_urls, _parent);
	}

	public static Method getMethod(Class<?> _clz, String _method, Class<?>[] _types)
	{
		try
		{
			if (_types == null)
				return _clz.getMethod(_method, new Class[] {});
			else
				return _clz.getMethod(_method, _types);
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static Method getMethod(Class<?> _clz, String _method, Object[] _arguments)
	{
		try
		{
			if (_arguments == null)
				return _clz.getMethod(_method, new Class[] {});
			else
			{
				Class<?>[] types = new Class[_arguments.length];
				for (int i = 0; i < _arguments.length; i++)
					types[i] = (_arguments[i] == null ? Object.class : _arguments[i].getClass());

				return _clz.getMethod(_method, types);
			}
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static Method getSingletonMethod(Class<?> _clz, String _method)
	{
		Method[] methods = _clz.getMethods();
		for (int i = 0; methods != null && i < methods.length; i++)
		{
			if (methods[i].getName().equals(_method))
				return methods[i];
		}

		return null;
	}

	public static Class<?> loadClass(String typeName, ClassLoader... classLoaders) throws ClassNotFoundException
	{
		Class<?> typeElement = basicTypes.get(typeName);
		if (typeElement != null)
			return typeElement;

		if (typeName.startsWith("[L"))
			typeName = typeName.substring(2);

		ClassLoader[] loaders = classLoaders == null || classLoaders.length <= 0 ? new ClassLoader[] { Thread.currentThread().getContextClassLoader(), ClassUtils.class.getClassLoader() } : classLoaders;
		for (ClassLoader classLoader : loaders)
		{
			if (classLoader == null)
				continue;
			try
			{
				return classLoader.loadClass(typeName);
			}
			catch (Throwable e)
			{
			}
		}

		throw new ClassNotFoundException("Class not found, <" + typeName + ">");
	}

	public static Object newInstance(Class<?> _clz)
	{
		return newInstance(_clz, null, null);
	}

	public static Object newInstance(Class<?> _clz, Class<?>[] _paramClassess, Object[] _paramValues)
	{
		try
		{
			if (_paramClassess == null || _paramValues == null)
				return _clz.newInstance();
			else
			{
				Constructor<?> constructor = _clz.getConstructor(_paramClassess);
				return constructor.newInstance(_paramValues);
			}
		}
		catch (Exception err)
		{
			throw new RuntimeException(err);
		}
	}

	public static URL getResource(String resourceName, ClassLoader... classLoaders)
	{
		ClassLoader[] loaders = classLoaders == null || classLoaders.length <= 0 ? new ClassLoader[] { Thread.currentThread().getContextClassLoader(), ClassUtils.class.getClassLoader() } : classLoaders;
		for (ClassLoader classLoader : loaders)
		{
			if (classLoader == null)
				continue;
			URL url = classLoader.getResource(resourceName);
			if (url != null)
				return url;
		}

		return null;
	}

	public static List<URL> getResources(String resourceName, ClassLoader... classLoaders)
	{
		List<URL> result = new ArrayList<URL>();
		ClassLoader[] loaders = classLoaders ;
		if( loaders==null || loaders.length==0 )
		{
			if( ClassUtils.class.getClassLoader().equals( Thread.currentThread().getContextClassLoader() ) )
				loaders = new ClassLoader[]{ClassUtils.class.getClassLoader()} ;
			else
				loaders = new ClassLoader[] { Thread.currentThread().getContextClassLoader(), ClassUtils.class.getClassLoader() }  ;
		}
		
		for (ClassLoader classLoader : loaders)
		{
			if (classLoader == null)
				continue;

			try
			{
				Enumeration<URL> urls = classLoader.getResources(resourceName);
				while (urls.hasMoreElements())
				{
					URL url = (URL) urls.nextElement();
					result.add(url);
				}
			}
			catch (IOException err)
			{
				LOG.error("Error occured, {}", err.getMessage());
				if (LOG.isDebugEnabled())
					LOG.error(null, err);
			}
		}
		return result;
	}

	public static List<URL> getClassPaths(ClassLoader... classLoaders)
	{
		List<URL> result = new ArrayList<URL>();

		ClassLoader[] loaders = classLoaders == null || classLoaders.length <= 0 ? new ClassLoader[] { Thread.currentThread().getContextClassLoader(), ClassUtils.class.getClassLoader() } : classLoaders;
		for (ClassLoader classLoader : loaders)
		{
			while (classLoader != null)
			{
				if ((classLoader instanceof URLClassLoader))
				{
					URL[] urls = ((URLClassLoader) classLoader).getURLs();
					if (urls != null)
					{
						for (URL url : urls)
							result.add(url);
					}
				}
				classLoader = classLoader.getParent();
			}
		}
		return result;
	}

	public static List<URL> getClassPathsFromEnv()
	{
		List<URL> result = new ArrayList<URL>();

		String javaClassPath = System.getProperty("java.class.path");
		if (javaClassPath != null)
		{
			for (String path : javaClassPath.split(File.pathSeparator))
			{
				try
				{
					result.add(new File(path).toURI().toURL());
				}
				catch (Exception err)
				{
					LOG.error("Error occured, {}", err.getMessage());
					if (LOG.isDebugEnabled())
						LOG.error(null, err);
				}
			}
		}
		return result;
	}

	public static List<URL> getClassPathsFromManifest()
	{
		try
		{
			return getClassPathsFromManifest(getClassPaths());
		}
		catch( IOException err )
		{
			return null ;
		}
	}

	public static List<URL> getClassPathsFromManifest(URL url) throws IOException
	{
		List<URL> result = new ArrayList<URL>();

		result.add(url);

		String part = cleanPath(url);
		File jarFile = new File(part);

		try( JarFile myJar = new JarFile(part) )
		{
			URL validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), part);
			if (validUrl != null)
			{
				result.add(validUrl);
			}
			Manifest manifest = myJar.getManifest();
			if (manifest != null)
			{
				String classPath = manifest.getMainAttributes().getValue(new Attributes.Name("Class-Path"));
				if (classPath != null)
				{
					for (String jar : classPath.split(" "))
					{
						validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), jar);
						if (validUrl != null)
							result.add(validUrl);
					}
				}
			}
		}

		return result;
	}

	public static List<URL> getClassPathsFromManifest(Iterable<URL> urls) throws IOException
	{
		List<URL> result = new ArrayList<URL>();
		for (URL url : urls)
			result.addAll(getClassPathsFromManifest(url));
		return result;
	}

	private static URL tryToGetValidUrl(String workingDir, String path, String filename)
	{
		try
		{
			if (new File(filename).exists())
				return new File(filename).toURI().toURL();

			if (new File(path + File.separator + filename).exists())
				return new File(path + File.separator + filename).toURI().toURL();

			if (new File(workingDir + File.separator + filename).exists())
				return new File(workingDir + File.separator + filename).toURI().toURL();

			if (new File(new URL(filename).getFile()).exists())
				return new File(new URL(filename).getFile()).toURI().toURL();
		}
		catch (MalformedURLException e)
		{
		}
		return null;
	}

	private static String cleanPath(URL url)
	{
		String path = url.getPath();
		try
		{
			path = URLDecoder.decode(path, "UTF-8");
		}
		catch (UnsupportedEncodingException e)
		{
		}

		if (path.startsWith("jar:"))
			path = path.substring("jar:".length());
		if (path.startsWith("file:"))
			path = path.substring("file:".length());
		if (path.endsWith("!/"))
			path = path.substring(0, path.lastIndexOf("!/")) + "/";

		return path;
	}
}